#include "stdafx.h"
#include "CScrLanguage.h"
#include "CParser.h"
#include "CCompiler.h"
#include "CScripts.h"

CLListEx<CScriptHeader::CSegment*>		CScriptHeader::s_Segments;

void CScript::Parse()
{
	CSubScript::Parse();
	/*for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
	{
		if(pLink->IsUsed())
		{
			pLink->Parse();
		}
	}*/
}
void CScript::Compile()
{
	if(IsMergedWith())
	{
		// This is the main script which is compiled first, so set up the header and initialise the compile size
		GetScript()->SetCompileSize(0);
		m_pHeader = new CScriptHeader();
	}

	CSubScript::Compile();
	/*for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
	{
		if(pLink->IsUsed())
		{
			pLink->Compile();
		}
	}*/
}
void CScript::Build()
{
	Parse();
	Compile();

	// BuildMainHeader();
	/*for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
	{
		//if(pLink->GetParseInfo()->
	}*/
}
void CScript::Merge()
{
	char filepath[MAX_PATH];
	const char * pName = GetFile()->GetName();
	strcpy(filepath, GetDirectoryPath());
	strncat(filepath, pName, strrchr(pName, '.') - pName);
	strcat(filepath, ".scm");
	
	if(CFile * pFile = new CFile(filepath, FILE_MODE_WRITE, FILE_TYPE_BINARY))
	{
		if(BYTE * pBuffer = new BYTE[SCRambl.Config.Performance.GetInputBufferSize()])
		{
			if(GetParseInfo() && GetParseInfo()->GetSize() && *m_szOutPath)
			{
				if(CFile * pSrcFile = new CFile(m_szOutPath, FILE_MODE_READ, FILE_TYPE_BINARY))
				{
					while(!pSrcFile->HasEnded())
						pFile->Write(pBuffer, pSrcFile->Read(pBuffer, SCRambl.Config.Performance.GetInputBufferSize()));
					Printf("Merged '%s' to '%s'", pSrcFile->GetName(), filepath);
					delete pSrcFile;
				}
			}

			for(auto pLink = m_SubScripts.Begin(); pLink != m_SubScripts.Bad(); ++pLink)
			{
				if(!pLink->IsUsed() || !pLink->GetParseInfo() || !pLink->GetParseInfo()->GetSize() || !*pLink->m_szOutPath)
					continue;
			
				if(CFile * pSrcFile = new CFile(pLink->m_szOutPath, FILE_MODE_READ, FILE_TYPE_BINARY))
				{
					while(!pSrcFile->HasEnded())
						pFile->Write(pBuffer, pSrcFile->Read(pBuffer, SCRambl.Config.Performance.GetInputBufferSize()));
					Printf("Merged '%s' to '%s'", pSrcFile->GetName(), filepath);
					delete pSrcFile;
				}
			}

			delete pBuffer;
		}

		delete pFile;
	}
	else THROW("Failed to create output file '%s'", filepath);
}

void CSubScript::Parse()
{
	// Do we even need to reparse this?
	if(IsParseNeeded())
	{
		Init();
		int nParsedBytes = Parser.Parse(this);
		auto time = GetTime();
		LastParseTime(time);
		GetParseInfo()->SetSize(nParsedBytes);
		GetParseInfo()->SetTimeParsed(time);
		DEBUG_TRACE("\n\n>> Preparsed %d bytes\n\n", nParsedBytes);
	
		// Add all tokens to the parse info - a linked list of tokens
		for(int x=0; x < Parser.s_nNumLinesParsed; x++)
		{
			for(int y=0; y < MAX_TOKENS; y++)
			{
				const CToken * pToken = Parser.GetTokenReadOnly(x, y);
				if(!pToken || pToken->GetType() == TOKEN_NONE) break;
				GetParseInfo()->Add(pToken);
			}
		}
	}
	else Printf("%s parse skipped", GetFile()->GetName());
}
void CSubScript::Compile()
{
	if(GetParseInfo() ? GetParseInfo()->GetSize() : false)
	{
		// If the original file is there we don't need to recompile
		if(*m_szOutPath)
		{
			auto pFile = new CFile(m_szOutPath, FILE_MODE_READ);
			if(pFile->OK())
			{
				delete pFile;
				Printf("%s compilation skipped", GetFile()->GetName());
				return;
			}
		}
		
		// Name the file either depending on the filename or a noname index (change this to use SCRIPT_NAME?)
		static int s_nNumNamelessScripts = 0;
		char filename[MAX_PATH];
		
		if(GetFile()->GetName()) szncpy(filename, GetFile()->GetName(), sizeof(filename)-4);
		else sprintf(filename, "noname%d", ++s_nNumNamelessScripts);
	
		strtok(filename, ".");
		strcat(filename, ".scc");
		
		// Try to create the output file
		CFile * pFile;
		if(!(pFile = new CFile(filename, FILE_MODE_WRITE)) || !pFile->OK())
			THROW("Couldn't create file '%s'", filename);

		// Initiate the compiler with the token list
		Compiler.Init(GetParseInfo()->Begin(), GetParseInfo()->Bad());
		
		// Set the begin offset to the current number of bytes compiled for merges
		if(GetScript()->IsMergedWith())
			GetParseInfo()->SetBeginOffset(GetScript()->GetCompileSize());

		// Compile and print results
		int nBytes = Compiler.Compile(this, pFile);
		Printf("Compiled %s (%d bytes)", GetFile()->GetName(), nBytes);
		
		// Increase the number of bytes compiled, so that 8 lines earlier we can use it
		GetScript()->SetCompileSize(GetScript()->GetCompileSize() + nBytes);
	}
	else Print("Nothing to compile!");
}
void CSubScript::Build()
{
	Parse();
	Compile();
}

void CScript::OnParse()
{

}

void CScriptParseInfo::OnParse()
{
}

void CSubScript::Add(CToken * pToken)
{
	if(pToken->IsSkippedDuringCompilation()) return;
	switch(pToken->GetType())
	{
	case TOKEN_NONE:
	case TOKEN_UNKNOWN:
		return;
	default:
		GetParseInfo()->Add(pToken);
	}
}
void CSubScript::Add(CLabel * pLabel)
{
	if(m_pScript->FindLabel(pLabel->GetName(), pLabel->GetHash()))
		THROW("Label '%s' already exists", pLabel->GetName());

	m_pScript->Add(pLabel);
	m_Labels.Add(pLabel->GetHash(), pLabel);
	m_LabelList.Add(pLabel);
}
void CSubScript::Add(CVariable * pVariable)
{
	if(pVariable->IsLocal())
	{
		if(!m_pScope)
			throw("LVAR declared outside of scope block");
		if(m_pScope->GetNumVariables() > CLimits::GetMaxScriptVar())
			throw("Number of LVAR's in current scope exceeds the limit");
		if(m_pScope->GetVariable(pVariable->GetName()))
			throw("An LVAR with this name already exists in the current scope");
			
		m_pScope->Add(pVariable);
	}
	else if(m_pScript)
		m_pScript->Add(pVariable);
}

void CSubScript::OnParse()
{
}

void CSubScript::OnParseEnd()
{
}

bool CScriptHeader::LoadFromFile(const char *szFilePath)
{
	if(CFile *pFile = new CFile(szFilePath, FILE_MODE_READ, FILE_TYPE_TEXT))
	{
		CSegment * pSegment = nullptr;

		while(pFile->ReadLine(g_szTempString, sizeof(g_szTempString)))
		{
			char * pStr = g_szTempString;
			pStr = ltrim(pStr);
			switch(*g_szTempString)
			{
			case ';':
			case '\n':
			case '\r':
			case '\0':
				continue;
			case '[':
				if(pStr = strtok(++pStr, "]"))
				{
					if(!strnicmp(pStr, "SEG", 3))
					{
						pStr += sizeof("SEG");
						pSegment = new CSegment(StringToInt(pStr));
						s_Segments.Add(pSegment);
					}
				}
				break;
			default:
				{
					CHeaderData * pData = new CHeaderData();

					if(pStr = strtok(pStr, " \t\r\n"))
					{
						int nSize = 0;
						if(!stricmp(pStr, "BYTE")) nSize = 1;
						else if(!stricmp(pStr, "WORD")) nSize = 2;
						else if(!stricmp(pStr, "DWORD")) nSize = 4;
						if(pData->m_nSize = nSize)
						{
							pData->m_eCmd = HEADER_CMD_MOV;
							pStr = strtok(nullptr, " \t\r\n");
							
							if(IsStringInt(pStr))
							{
								pData->m_nVal = StringToInt(pStr);
							}
							else
							{
								if(!stricmp(pStr, "SEGID")) pData->m_eVar = HEADER_VAR_SEGID;
								else if(!stricmp(pStr, "TARGETGAME")) pData->m_eVar = HEADER_VAR_TARGETGAME;
								else if(!stricmp(pStr, "MAINSIZE")) pData->m_eVar = HEADER_VAR_MAINSIZE;
								else if(!stricmp(pStr, "LARGESTMISSIONSIZE")) pData->m_eVar = HEADER_VAR_LARGESTMISSIONSIZE;
								else if(!stricmp(pStr, "NUMMISSIONS")) pData->m_eVar = HEADER_VAR_NUMMISSIONS;
								else if(!stricmp(pStr, "NUMMODELS")) pData->m_eVar = HEADER_VAR_NUMMODELS;
								else if(!stricmp(pStr, "HIGHESTNUMLVARS")) pData->m_eVar = HEADER_VAR_HIGHESTNUMLVARS;
								else if(!stricmp(pStr, "LARGESTSTREAMEDSCRIPTSIZE")) pData->m_eVar = HEADER_VAR_LARGESTSTREAMEDSCRIPTSIZE;
								else if(!stricmp(pStr, "LARGESTSTREAMEDSCRIPTSIZE")) pData->m_eVar = HEADER_VAR_NUMSTREAMEDSCRIPTS;
								else if(!stricmp(pStr, "VARSPACESIZE")) pData->m_eVar = HEADER_VAR_VARSPACESIZE;
								else pData->m_eVar = HEADER_VAR_NONE;
							}
						}
						else
						{
							if(!stricmp(pStr, "VAR_SPACE")) pData->m_eCmd = HEADER_CMD_VAR_SPACE;
							else if(!stricmp(pStr, "MODEL_NAMES")) pData->m_eCmd = HEADER_CMD_MODEL_NAMES;
							else if(!stricmp(pStr, "MISSION_OFFSETS")) pData->m_eCmd = HEADER_CMD_MISSION_OFFSETS;
							else if(!stricmp(pStr, "STREAMED_SCRIPT_INFO")) pData->m_eCmd = HEADER_CMD_STREAMED_SCRIPT_INFO;
							
							if(pStr = strtok(nullptr, " \t\r\n"))
							{
								if(IsStringInt(pStr)) pData->m_nVal = StringToInt(pStr);
							}
						}
					}

					pSegment->m_Data.Add(pData);
				}
			}
		}
		delete pFile;
		return true;
	}
	else throw("Failed to open file");
	return false;
}